<?php
/*------------------------------------------------------------------------------
Copyright (C) 2006 J.Ducastel fredbird@fredbird.org

object-oriented, domxml, curl based xml-rpc library

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
------------------------------------------------------------------------------*/

/**
*
* @author J.Ducastel <fredbird@fredbird.org>
* @version
*/
class ooXmlRpcServer {
/*------------------------------------------------------------------------------
                                                                     constructor
------------------------------------------------------------------------------*/
	/**
	*
	* @access
	*/
	function ooXmlRpcServer($iniFile=null) {
		$this->__construct($iniFile);
	}/**/

	/**
	*
	* @access
	*/
	function __construct($iniFile=null) {
		//$this->deb=&wak::getservice('wakdebug');
		$this->loadConfig($iniFile);
	}/**/
/*------------------------------------------------------------------------------
                                                                  public methods
------------------------------------------------------------------------------*/
	/**
	* Tries to serve current http xml-rpc method call
	* @access public
	* @return bool
	*/
	function serveRequest() {
		//$this=&domXmlRpcServer::_getsingleton();
		// check for http authentication
		if (!$this->authenticationCheck($this->cfg['users'])) {
			// return $this->raiseFa
			return false;
		}
		// retrieve and build method call
		if (!$call=&$this->getMethodCall()) {
			// failed, fault have been raised by getMethodCall
			// echo "invalid call : ".$this->deb->vardump($call);
			return false;
		}
		// runs call
		// echo $this->deb->vardump($this);
		if (!$response=&$this->ResponseToCall($call)) {
			// failed, raise fault
			return false;
		}
		// echo $deb->vardump($cm);
		// sending response
		return $this->sendResponse($response);
	}/**/

	/**
	* build and send methodResponse including fault
	* @param int $code faultCode
	* @param string $string faultString
	* @access public
	* @return bool
	*/
	function raiseFault($code,$string=null) {
		// has response already been sent ?
		if ($this->responseSent)
			return false;
		// getting predefined desc if required and available
		if (is_null($string) and isset($this->cfg['faults'][$code]))
			$string=$this->cfg['faults'][$code];
		$fault=&new ooXmlRpcFault($code,$string);
		if (!$fault->isValid()) {
			return false;
		}
		$response=&new ooXmlRpcMethodResponse();
		//$responseNode=&$response->getNode();
		//$fault->appendToNode($responseNode);
		$response->setContent($fault);
		return $this->sendResponse($response);
	}/**/



	/**
	*
	* @param
	* @access public
	* @return
	* /
	function () {

	}/**/
/*------------------------------------------------------------------------------
                                                                      properties
------------------------------------------------------------------------------*/
	/**
	* @var array configuration data
	* 'service_classes'=> array('serviceName'=>'class')
	* 'service_files'=> array('serviceName'=>'file')
	*/
	var $cfg=array(
		// http authentication config
		'auth'=>array(
			'use'=>false
			, 'pwd_file'=>'')
		, 'users'=>array()
		// classes associated to services
		, 'service_classes'=>array()
		// files defining service classes
		, 'service_files'=>array()
		// default faultstring for fault codes
		, 'faults'=>array(
			400=>'bad request'
			, 401=>'unauthorized'
			, 403=>'forbidden'
			, 404=>'not found'
			, 500=>'internal server error'
			, 501=>'not implemented'
			, 503=>'service unavailable'));
	/**
	* @var bool has methodResponse be sent ?
	*/
	var $responseSent=false;

	/**
	* @var type desc
	*/
/*------------------------------------------------------------------------------
                                                                 private methods
------------------------------------------------------------------------------*/
	/**
	* build response to call
	* find and execute call's implementation
	* @param ooxmlrpcmethodcall &$call
	* @access protected
	* @return ooxmlrpcmethodresponse
	*/
	function & responseToCall(&$call) {
		// $deb=&wak::getservice('wakdebug');
		$response=false;
		if (!is_a($call,'ooxmlrpcmethodcall')) {
			// bad argument
			$this->raiseFault(500);
			return $response;
		}
		$method=$call->getmethod(); //echo "<p>method: $method</p>";
		$service=$call->getServiceName(); //echo "<p>service: $service</p>";
		// get params
		$params=&$call->getParams();
		// build response
		$response=&new ooXmlRpcMethodResponse();
		// is call service based ?
		if ($service) {
			// include service file
			if (isset($this->cfg['service_files'][$service])) {
				$files=explode(' ',$this->cfg['service_files'][$service]);
				foreach ($files as $file) {
					if (!file_exists($file)) {
						// trigger_error("ooXmlRpcServer : required file $file don't exist",E_USER_WARNING);
						// return false;
					}
					include_once($file);
				}
			}
			// get service class
			if (isset($this->cfg['service_classes'][$service]))
				$class=$this->cfg['service_classes'][$service];
			else $class=$service;
			// is class defined ?
			if (!class_exists($class)) {
				// not defined, raising fault
				$str="ooXmlRpcServer : class $class undefined";
				$this->raiseFault(503,$str);
				return false;
			}
			// is method defined ?
			if (!in_array($method,get_class_methods($class))) {
				// method not implemented, raising fault
				$this->raiseFault(501);
				return false;
			}
			// run class method
			$result=eval('return '.$class.'::'.$method.'($params);');
			if (!$response->setContent($result)) {
				// invalid result, raise fault
				//echo '<p>invalid result : '.$deb->vardump($result).'</p>';
				$this->raiseFault('500','bad method result');
			}
			//$deb=&wak::getservice('wakdebug');
			//echo $deb->vardump($result);
			// echo wakaffvar($response);
		} else {
			// searching within method_functions
			// searching within own methods
			// not found
			// method not implemented, raising fault
			$this->raiseFault(501);
			return false;
		}
		return $response;
	}/**/

	/**
	* retrieves and build method call from http raw post data
	* @param
	* @access protected
	* @return domXmlRpcMethodCall|false
	*/
	function & getMethodCall() {
		// echo "getting call !";
		//$this=&domXmlRpcServer::_getsingleton();
		$call=false;
		if (!$xml=&$this->getrawpost()) {
			// no data at all,  raising fault
			$this->raiseFault(400,'no xml data');
			return $call;//
		}
		// building call instance
		$call=& new ooXmlRpcMethodCall();
		if (!$call->setFromXml($xml)) {
			// failed to retrieve xml, raising fault
			$this->raiseFault(400,'invalid xml data');
			$call=false;
		}
		return $call;
	}/**/

	/**
	* returns raw post data
	* @param
	* @access protected
	* @return string|false
	*/
	function getRawPost() {
		if (!isset($GLOBALS['HTTP_RAW_POST_DATA'])
		or !$raw=$GLOBALS['HTTP_RAW_POST_DATA']) {
			// hack to retrieve raw data
			$raw=file_get_contents('php://input');
		}
		return $raw;
	}/**/

	/**
	* sends method response to client
	* @param
	* @access protected
	* @return
	*/
	function sendResponse(&$response) {
		if ($this->responseSent)
			return false;
		$xml=$response->getxml();
		// sending headers
		header('Content-type: text/xml');
		// sending content
		//$deb=&wak::getservice('wakdebug');
		//echo $deb->vardump($xml,'html',5000);
		echo $xml;
		$this->responseSent=true;
		return true;
	}/**/

	/**
	* loads config from ini file, merge data into $this->cfg
	* search ini file at cfg/{serverclass}.cfg.ini
	* @param string $file
	* @access protected
	* @return bool
	*/
	function loadConfig($file=null) {
		if (is_null($file))
			$file='cfg/'.get_class($this).'.cfg.ini'; //echo $file;
		if (file_exists($file) and $cfg=parse_ini_file($file,true)) {
			// merging new config with default one
			$this->cfg=array_merge_recursive($this->cfg,$cfg);
			//trigger_error("loaded config from $file");
			return true;
		}
		return false;
	}/**/

	/**
	* checks for http authentication
	* @param array users user => pwd list of authorized users with pwd
	* @access protected
	* @return bool
	*/
	function authenticationCheck($users=array()) {
		// is authentication required ?
		if (!$this->cfg['auth']['use'])
			return true;
		// are login/pwd provided ?
		if (!isset($_SERVER['PHP_AUTH_USER'])
		or !isset($_SERVER['PHP_AUTH_PW'])) {
			$realm='realm';
			//header( 'WWW-Authenticate: Basic realm="'.$realm.'"' );
			header( 'WWW-Authenticate: Basic' );
			header( 'HTTP/1.0 401 Unauthorized' );
			//echo 'Authorization Required.';
			$this->raiseFault(401);
			return false;
			exit;
		} else {
			// checking for provided login/pwd
			$provided_login=$_SERVER['PHP_AUTH_USER'];
			$provided_pwd=$_SERVER['PHP_AUTH_PW'];
			// searching for user/pwd
			foreach ($users as $login => $pwd) {
				if ($login==$provided_login and $pwd==$provided_pwd)
					return true;
			}
			// invalid user/pwd
			$this->raiseFault(401);
			return false;
		}
		
		return false;
	}/**/

	/**
	*
	* @param
	* @access protected
	* @return
	* /
	function () {

	}/**/

	/**
	*
	* @param
	* @access protected
	* @return
	* /
	function () {

	}/**/
/*------------------------------------------------------------------------------
                                                                 private methods
------------------------------------------------------------------------------*/
	/**
	*
	* @param
	* @access private
	* @return
	* /
	function () {

	}/**/

	/**
	*
	* @param
	* @access private
	* @return
	* /
	function () {

	}/**/
}

/**
*
* @author J.Ducastel <fredbird@fredbird.org>
* @version
*/
class ooXmlRpcService {
/*------------------------------------------------------------------------------
                                                                     constructor
------------------------------------------------------------------------------*/
	/**
	*
	* @access
	*/
	function ooXmlRpcClient(&$server) {
		$this->__construct($server);
	}/**/

	/**
	*
	* @access
	*/
	function __construct(&$server) {
		$this->server=&$server;
	}/**/
/*------------------------------------------------------------------------------
                                                                  public methods
------------------------------------------------------------------------------*/
	/**
	*
	* @param
	* @access public
	* @return
	*/
	function raiseFault($code,$string=null) {
		return $this->server->raiseFault($code,$string);
	}/**/

	/**
	*
	* @param
	* @access public
	* @return
	* /
	function () {

	}/**/
/*------------------------------------------------------------------------------
                                                                      properties
------------------------------------------------------------------------------*/
	/**
	* @var ooXmlRpcServer reference to server
	*/
	var $server;

/*------------------------------------------------------------------------------
                                                                 private methods
------------------------------------------------------------------------------*/


	/**
	*
	* @param
	* @access protected
	* @return
	* /
	function () {

	}/**/
/*------------------------------------------------------------------------------
                                                                 private methods
------------------------------------------------------------------------------*/
	/**
	*
	* @param
	* @access private
	* @return
	* /
	function () {

	}/**/

	/**
	*
	* @param
	* @access private
	* @return
	* /
	function () {

	}/**/
}

/**
*
* @author J.Ducastel <fredbird@fredbird.org>
* @version
*/
class ooXmlRpcClient {
/*------------------------------------------------------------------------------
                                                                     constructor
------------------------------------------------------------------------------*/
	/**
	*
	* @access
	* /
	function ooXmlRpcClient() {
		$this->__construct();
	}/**/

	/**
	*
	* @access
	* /
	function __construct() {

	}/**/
/*------------------------------------------------------------------------------
                                                                  public methods
------------------------------------------------------------------------------*/
	/**
	* sets url for xml-rpc calls
	* @param string $url
	* @access public
	* @return bool
	*/
	function setUrl($url) {
		$this->url=$url;
		return true;
	}/**/

	/**
	* builds methodCall from method name + params
	* @param string $methodName
	* @param array optional, parameters array
	* @access public
	* @return bool
	*/
	function setCall($methodName,$params=array()) {
		// searching for custom call-building method
		// fall back to standard method
		if (!$call=$this->buildCall($methodName,$params)) {
			return false;
		}
		$this->methodCall=&$call;
		//echo wakaffvar($call);
		return true;
	}/**/

	/**
	* imports pre-built methodCall
	* @param ooXmlRpcMethodCall
	* @access public
	* @return bool
	*/
	function setMethodCall(& $methodCall) {
		if (!is_a($methodCall,'ooXmlRpcMethodCall'))
			return false;
		$this->methodCall=&$methodCall;
		return true;
	}/**/

	/**
	* sets http authentification data
	* @param
	* @access public
	* @return bool
	*/
	function setAuth($login,$pwd) {
		if (!$login or !$pwd) {
			return false;
		}
		$this->auth=$login.':'.$pwd;
		return true;
	}/**/

	/**
	* executes call
	* @access public
	* @return bool
	*/
	function issueCall() {
		// getting http request result
		if (!$this->getHttpResponse()) {
			return false;
		}
		// building methodResponse
		//$this->
		return true;
	}/**/

	/**
	*
	* @param
	* @access public
	* @return
	*/
	function getHttpResponse() {
		if (!$this->httpResponse) {
			if (!is_a($this->methodCall,'ooXmlRpcMethodCall')) {
				return false;
			}
			$xml=$this->methodCall->getXml(); //echo wakaffvar($xml);
			$this->httpResponse=$this->httpXmlRpcRequest($xml,$this->url);
		}
		return $this->httpResponse;
	}/**/

	/**
	* returns response as an object
	* @access public
	* @return ooXmlRpcMethodResponse
	*/
	function getMethodResponse() {
		if (!$this->methodResponse) {
			$xml=$this->getHttpResponse();
			$response=new ooXmlRpcMethodResponse();
			if ($response->setFromXml($xml))
				$this->methodResponse=&$response;
		}
		return $this->methodResponse;
	}/**/

	/**
	*
	* @param
	* @access public
	* @return
	* /
	function () {

	}/**/


/*------------------------------------------------------------------------------
                                                                      properties
------------------------------------------------------------------------------*/
	/**
	* @var string default/current url
	*/
	var $url;
	/**
	* @var ooXmlRpcMethodCall call instance
	*/
	var $methodCall;
	/**
	* @var string http response content
	*/
	var $httpResponse;
	/**
	* @var ooXmlRpcMethodResponse response instance
	*/
	var $methodResponse;
	/**
	* @var string http authentication as login:pwd
	*/
	var $auth;
	/**
	* @var type desc
	*/
	/**
	* @var type desc
	*/
/*------------------------------------------------------------------------------
                                                                 private methods
------------------------------------------------------------------------------*/
	/**
	* Issue server xml-rpc request and return http response content
	* uses curl library
	* @param string $url xml-rpc url
	* @param string $xml complete methodCall xml
	* @access protected
	* @return string
	*/
	function httpXmlRpcRequest($xml,$url=null) {
		if (is_null($url))
			$url=$this->url;
		// init curl session
		$ch =&curl_init($url);
		// setting correct xml-rpc parameters
		// POST call
		curl_setopt($ch,CURLOPT_POST,true);
		// headers
		$headers=array(
			'Content-type : text/xml'
		);
		curl_setopt($ch,CURLOPT_HTTPHEADER,$headers);
		// setting raw POST data
		curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
		// http authentication
		if ($this->auth)
			curl_setopt($ch,CURLOPT_USERPWD,$this->auth);

		// executing http call
		// returning content rather than printing it
		curl_setopt($ch,CURLOPT_RETURNTRANSFER,true);
		$response=curl_exec($ch);
		$info=curl_getinfo($ch);
		//$deb=wak::getservice('wakdebug');
		// echo $deb->vardump($info);
		// echo wakaffvar($info);
		curl_close($ch);
		return $response;
	}/**/

	/**
	* builds methodCall
	* @param string $name
	* @access protected
	* @return ooXmlRpcMethodCall
	* /
	function buildCall($name,$params=array()) {

	}/**/

	/**
	* returns response to methodcall, as xml
	* @param
	* @access protected
	* @return
	*/
	function & getResponseXmlToCall($methodCall,$url=null) {
		if (is_null($url))
			$url=$this->url;
		// getting xml
		$callXml=$methodCall->getXml(); //echo $callXml;
		// sending call
		$responseXml=$this->httpXmlRpcRequest($callXml,$url);
		// echo $responseXml;
		return $responseXml;
	}/**/

	/**
	* returns response to methodcall, as methodResponse
	* @param
	* @access protected
	* @return
	*/
	function & getResponseToCall(& $methodCall, $url=null) {
		if (is_null($url))
			$url=$this->url;
		$xml=$this->getResponseXmlToCall($methodCall,$url);
		// echo htmlentities($responseXml);
		//echo $responseXml;
		// building response
		$response=new ooXmlRpcMethodResponse();
		$response->setFromXml($xml);
		// echo wakaffvar($response);
		return $response;
	}/**/

	/**
	* builds methodCall instance
	* @param string $methodName
	* @param array $params
	* @access protected
	* @return ooXmlRpcMethodCall
	*/
	function & buildCall($methodName,$params=array()) {
		$call=new ooXmlRpcMethodCall();
		$call->setName($methodName);
		for ($n=0; $n<count($params); $n++) {
			$call->params->setParam($params[$n]);
		}
		// echo wakaffvar($call);
		return $call;
	}/**/

	/**
	*
	* @param
	* @access protected
	* @return
	* /
	function () {

	}/**/
/*------------------------------------------------------------------------------
                                                                 private methods
------------------------------------------------------------------------------*/
	/**
	*
	* @param
	* @access private
	* @return
	* /
	function () {

	}/**/

	/**
	*
	* @param
	* @access private
	* @return
	* /
	function () {

	}/**/
}

/**
*
* @author J.Ducastel <fredbird@fredbird.org>
* @version
*/
class ooXmlRpcDomNode {
/*------------------------------------------------------------------------------
                                                                     constructor
------------------------------------------------------------------------------*/
	/**
	*
	* @access
	*/
	function ooXmlRpcDomNode(&$node) {
		$this->__construct($node);
	}/**/

	/**
	*
	* @access
	*/
	function __construct(&$node) {
		$this->node=&$node;
		$this->doc=&$this->node->owner_document();
		$this->xContext=&xpath_new_context($this->doc);
	}/**/
/*------------------------------------------------------------------------------
                                                                  public methods
------------------------------------------------------------------------------*/
	/**
	* return xpath selected node(s) from domDocument
	* @param domNode $
	* @param string xpath
	* @param bool $singleResult optional, return directly first result nodes
	* @access public
	* @return array
	*/
	function getXpathNodes($xpath) {
		//echo "<p>searching $xpath from </p><pre>";
		//var_dump($this->node);
		// getting results
		if (!$results=$this->xContext->xpath_eval($xpath,$this->node)
			or count($results->nodeset)<1)
			// no results
			return false;
		//return $results->nodeset;
		$nodes=array();
		for ($n=0; $n<count($results->nodeset); $n++) {
			$node=&new ooXmlRpcDomNode($results->nodeset[$n]);
			$nodes[]=&$node;
		}
		return $nodes;
	}/**/

	/**
	* return self node's content
	* @access public
	* @return mixed
	*/
	function getContent() {
		return $this->node->get_content();
	}/**/

	/**
	* sets self node's content
	* @param mixed &$content
	* @access public
	* @return bool
	*/
	function setContent(&$content) {
		return $this->node->set_content($content);
	}/**/

	/**
	*
	* @param
	* @access public
	* @return
	*/
	function getTagName() {
		return $this->node->tagname();
	}/**/

	/**
	* return xml dump of the node
	* @param
	* @access public
	* @return string
	* /
	function getXml() {
		if (is_a($this->node,'domdocument'))
			return $this->node->dump_mem();
		return $this->node->dump_node($this->doc);
	}/**/

	/**
	* return domDocument reference
	* @access public
	* @return domDocument
	*/
	function & getDomDoc() {
		return $this->doc;
	}/**/

	/**
	* build, append and return node's child
	* @param string tagName
	* @access public
	* @return & ooXmlRpcDomNode
	*/
	function & makeChild($tagName) {
		// build dom node
		$dom=$this->doc->create_element($tagName);
		//append domnode
		$this->node->append_child($dom);
		$node=&new ooXmlRpcDomNode($dom);
		return $node;
	}/**/

	/**
	*
	* @param
	* @access public
	* @return
	* /
	function () {

	}/**/
/*------------------------------------------------------------------------------
                                                                      properties
------------------------------------------------------------------------------*/
	/**
	* @var type desc
	*/
	var $node;
	var $doc; // root domDocument reference
	var $xContext; // xpathcontext instance

/*------------------------------------------------------------------------------
                                                                 private methods
------------------------------------------------------------------------------*/
	/**
	*
	* @param
	* @access protected
	* @return
	* /
	function () {

	}/**/

	/**
	*
	* @param
	* @access protected
	* @return
	* /
	function () {

	}/**/
/*------------------------------------------------------------------------------
                                                                 private methods
------------------------------------------------------------------------------*/
	/**
	*
	* @param
	* @access private
	* @return
	* /
	function () {

	}/**/

	/**
	*
	* @param
	* @access private
	* @return
	* /
	function () {

	}/**/
}

/**
*
* @author J.Ducastel <fredbird@fredbird.org>
* @version
*/
class ooXmlRpcMethodCall {
/*------------------------------------------------------------------------------
                                                                     constructor
------------------------------------------------------------------------------*/
	/**
	*
	* @access
	*/
	function ooXmlRpcMethodCall() {
		$this->__construct();
	}/**/

	/**
	*
	* @access
	*/
	function __construct() {
		$this->params=&new ooXmlRpcParams();
	}/**/
/*------------------------------------------------------------------------------
                                                                  public methods
------------------------------------------------------------------------------*/
	/**
	* sets content from xml string
	* @param string $xml
	* @access public
	* @return bool
	*/
	function setFromXml($xml) {
		// creating domDocument instance
		if (!$domDocument=&domxml_open_mem($xml)) {
			return false;
		}
		// building
		$callNode=new ooXmlRpcDomNode($domDocument);
		//$deb=&wak::getservice('wakdebug');
		// setting method name
		if (!$nodes=$callNode->getxpathnodes('/methodCall/methodName/text()')) {
			return false;
		}
		$this->name=$nodes[0]->getContent();
		// getting params
		// $this->params=&new
		if ($nodes=$callNode->getxpathnodes('/methodCall/params')) {
			//echo $deb->vardump($nodes);
			//$pNode=&new ooXmlRpcDomNode($nodes[0]);
			$this->params->setFromNode($nodes[0]);
		}
		//$this->params=&new domXmlRpcParams();
		return true;
	}/**/

	/**
	* returns full methodName
	* @param
	* @access public
	* @return
	*/
	function getName() {
		return $this->name;
	}/**/

	/**
	* sets methodName
	* @param string $name
	* @access public
	* @return
	*/
	function setName($name) {
		if (!is_string($name))
			return false;
		$this->name=$name;
		return true;
	}/**/

	/**
	* returns method part of methodname (after the point='.')
	* @param
	* @access public
	* @return string|false
	*/
	function getMethod() {
		if (!eregi('([a-z][a-z0-9]*)$',$this->name,$result))
			return false;
		return $result[1];
	}/**/

	/**
	* returns service part of methodname (before the point='.')
	* @access public
	* @return string|false
	*/
	function getServiceName() {
		if (!eregi('^([a-z][a-z0-9]*)\.',$this->name,$result))
			return false;
		return $result[1];
	}/**/

	/**
	* return call's domXmlRpcParams instance
	* @param
	* @access public
	* @return domXmlRpcParams
	*/
	function & getParams() {
		return $this->params;
	}/**/

	/**
	* return content as xml
	* @param
	* @access public
	* @return string
	*/
	function getXml() {
		// get own, root node
		if (!$rootNode=&$this->getNode())
			return false;
		// append name
		$nameNode=$rootNode->makeChild('methodName');
		$nameNode->setContent($this->name);
		// append params
		$this->params->appendToNode($rootNode);
		// append content
		//if (!$this->content)
			//return false;
		//$this->content->appendToNode($rootNode);
		// build and return xml
		$doc=$rootNode->getDomDoc();
		return $doc->dump_mem(1);
	}/**/

	/**
	*
	* @param
	* @access public
	* @return
	* /
	function () {

	}/**/
/*------------------------------------------------------------------------------
                                                                      properties
------------------------------------------------------------------------------*/
	/**
	* @var type desc
	*/
	var $name; // methodName
	var $params; // ooXmlRpcParams instance
/*------------------------------------------------------------------------------
                                                                 private methods
------------------------------------------------------------------------------*/
/**
	* build and return ooXmlRpcNode for content dom manipulation
	* @param
	* @access protected
	* @return ooxmlrpcnode
	*/
	function & getNode() {
		$node=false;
		$baseXml='<?xml version="1.0" encoding="utf-8"?><methodCall />';
		if (!$dom=&domxml_open_mem($baseXml))
			return $node;
		$doc=&new ooXmlRpcDomNode($dom);
		$roots=&$doc->getxpathnodes('/*');
		$node=$roots[0];

		return $node;
	}/**/

	/**
	*
	* @param
	* @access protected
	* @return
	* /
	function () {

	}/**/
/*------------------------------------------------------------------------------
                                                                 private methods
------------------------------------------------------------------------------*/
	/**
	*
	* @param
	* @access private
	* @return
	* /
	function () {

	}/**/

	/**
	*
	* @param
	* @access private
	* @return
	* /
	function () {

	}/**/
}

/**
*
* @author J.Ducastel <fredbird@fredbird.org>
* @version
*/
class ooXmlRpcMethodResponse {
/*------------------------------------------------------------------------------
                                                                     constructor
------------------------------------------------------------------------------*/
	/**
	*
	* @access
	* /
	function ooXmlRpcMethodResponse() {
		$this->__construct();
	}/**/

	/**
	*
	* @access
	* /
	function __construct() {

	}/**/
/*------------------------------------------------------------------------------
                                                                  public methods
------------------------------------------------------------------------------*/
	/**
	* sets content :
	* @param ooxmlrpcparams|ooxmlrpcfault &$content
	* @access public
	* @return bool
	*/
	function setContent(&$content) {
		// check input
		if (!is_a($content,'ooxmlrpcparams') and !is_a($content,'ooxmlrpcfault'))
			return false;
		$this->content=&$content;
		return true;
	}/**/

	/**
	* build and return xml
	* @param
	* @access public
	* @return string
	*/
	function getXml() {
		// get own, root node
		if (!$rootNode=&$this->getNode())
			return false;
		// append content
		if ($this->content)
			$this->content->appendToNode($rootNode);
		// build and return xml
		$doc=$rootNode->getDomDoc();
		return $doc->dump_mem(1);
	}/**/

	/**
	* Return values as an array, if content is set as ooXmlRpcParams
	* @access public
	* @return array
	*/
	function getValues() {
		if (!is_a($this->content,'ooxmlrpcparams'))
			return false;
		return $this->content->getvalues();
	}/**/

	/**
	* Sets from xml string
	* @param string $xml
	* @access public
	* @return bool
	*/
	function setFromXml($xml) {
		// creating domDocument instance
		if (!$domDocument=&domxml_open_mem($xml)) {
			//trigger_error("ooXmlRpcMethodResponse : invalid xml ");
			// echo htmlentities($xml);
			return false;
		}
		// building
		$responseNode=new ooXmlRpcDomNode($domDocument);
		//$deb=&wak::getservice('wakdebug');
		// params or fault ?
		if ($nodes=$responseNode->getXpathNodes('/methodResponse/fault')) {
			// fault
			$this->content=new ooXmlRpcFault();
			$this->content->setFromNode($nodes[0]);
		} else if ($nodes=$responseNode->getxpathNodes('/methodResponse/params')) {
			// params
			$this->content=ooXmlRpcParams();
			//$pNode=&new ooXmlRpcDomNode($nodes[0]);
			$this->content->setFromNode($nodes[0]);
		} else {
			// empty response ???
			return true;
			//return false;
		}
		//$this->params=&new domXmlRpcParams();
		return true;
	}/**/

	/**
	*
	* @param
	* @access public
	* @return
	* /
	function () {

	}/**/
/*------------------------------------------------------------------------------
                                                                      properties
------------------------------------------------------------------------------*/
	/**
	* @var type desc
	*/
	var $content; // ooXmlRpcMethodParams|ooXmlRpcMethodFault

/*------------------------------------------------------------------------------
                                                                 private methods
------------------------------------------------------------------------------*/
	/**
	* build and return ooXmlRpcNode for content dom manipulation
	* @param
	* @access protected
	* @return ooxmlrpcnode
	*/
	function & getNode() {
		$node=false;
		$baseXml='<?xml version="1.0" encoding="utf-8"?><methodResponse />';
		if (!$dom=&domxml_open_mem($baseXml))
			return $node;
		$doc=&new ooXmlRpcDomNode($dom);
		$roots=&$doc->getxpathnodes('/*');
		$node=$roots[0];

		return $node;
	}/**/

	/**
	*
	* @param
	* @access protected
	* @return
	* /
	function () {

	}/**/
/*------------------------------------------------------------------------------
                                                                 private methods
------------------------------------------------------------------------------*/
	/**
	*
	* @param
	* @access private
	* @return
	* /
	function () {

	}/**/

	/**
	*
	* @param
	* @access private
	* @return
	* /
	function () {

	}/**/
}

/**
*
* @author J.Ducastel <fredbird@fredbird.org>
* @version
*/
class ooXmlRpcParams {
/*------------------------------------------------------------------------------
                                                                     constructor
------------------------------------------------------------------------------*/
	/**
	*
	* @access
	* /
	function ooXmlRpcParams() {
		$this->__construct();
	}/**/

	/**
	*
	* @access
	* /
	function __construct() {

	}/**/
/*------------------------------------------------------------------------------
                                                                  public methods
------------------------------------------------------------------------------*/
	/**
	* sets value(s) from node
	* @param ooXmlRpcNode &$node
	* @access public
	* @return bool
	*/
	function setFromNode(&$node) {
		if (!is_a($node,'ooxmlrpcdomnode')) {
			return false;
		}
		//var_dump($node);
		// searching for values
		if ($vNodes=$node->getxpathnodes('child::param/value')) {
			for ($n=0; $n<count($vNodes); $n++) {
				$value=&new ooXmlRpcValue();
				//$vNode=&new ooXmlRpcDomNode($valueNodes[$n]);
				$value->setFromNode($vNodes[$n]);
				$this->values[]=&$value;
			}
		}
		return true;
	}/**/

	/**
	* build and appends self node(s) to parent node
	* @param
	* @access public
	* @return bool
	*/
	function appendToNode(&$parentNode) {
		// build & append self
		$selfNode=&$parentNode->makeChild('params');
		// build & append values to self
		for ($n=0; $n<count($this->values); $n++) {
			$paramNode=$selfNode->makeChild('param');
			$this->values[$n]->appendToNode($paramNode);
		}
		return true;
	}/**/

	/**
	* sets a param's value
	* @param ooXMlRpcValue|mixed $value either ooXmlRpcValue instance, or php var
	* @param int $index param index, if ommitted will be instered at end of array
	* @access public
	* @return bool
	*/
	function setParam($value,$index=null) {
		// building value instance if necessary
		if (!is_a($value,'ooXmlRpcValue')) {
			$valueO=new ooXmlRpcValue();
			if (!$valueO->setValue($value))
				return false;
			$value=&$valueO;
		}
		if (is_int($index))
			$this->values[$index]=$value;
		else
			$this->values[]=&$value;
		return true;
	}/**/

	/**
	* return values as an array of php vars
	* @access public
	* @return array
	*/
	function & getValues() {
		$values=array();
		for ($n=0; $n<count($this->values); $n++)
			$values[]=$this->values[$n]->getValue();
		return $values;
	}/**/

	/**
	*
	* @param
	* @access public
	* @return ooXmlRpcValue
	*/
	function & getParam($index) {
		$r=false;
		if (isset($this->values[$index]))
			$r=$this->values[$index];
		return $r;
	}/**/

	/**
	*
	* @param
	* @access public
	* @return
	* /
	function () {

	}/**/
/*------------------------------------------------------------------------------
                                                                      properties
------------------------------------------------------------------------------*/
	/**
	* @var type desc
	*/
	var $values=array();

/*------------------------------------------------------------------------------
                                                                 private methods
------------------------------------------------------------------------------*/
	/**
	*
	* @param
	* @access protected
	* @return
	* /
	function () {

	}/**/

	/**
	*
	* @param
	* @access protected
	* @return
	* /
	function () {

	}/**/
/*------------------------------------------------------------------------------
                                                                 private methods
------------------------------------------------------------------------------*/
	/**
	*
	* @param
	* @access private
	* @return
	* /
	function () {

	}/**/

	/**
	*
	* @param
	* @access private
	* @return
	* /
	function () {

	}/**/
}

/**
* handle for <fault> elements
* @author J.Ducastel <fredbird@fredbird.org>
* @version
*/
class ooXmlRpcFault{
/*------------------------------------------------------------------------------
                                                                     constructor
------------------------------------------------------------------------------*/
	/**
	*
	* @access public
	*/
	function ooXmlRpcFault($code=NULL,$string=NULL) {
		$this->__construct($code,$string);
	}/**/

	/**
	*
	* @access public
	*/
	function __construct($code=NULL,$string=NULL) {
		if ($code)
			$this->setCode($code);
		if ($string)
			$this->setString($string);
	}/**/
/*------------------------------------------------------------------------------
                                                                  public methods
------------------------------------------------------------------------------*/
	/**
	* sets faultCode
	* @param int $code
	* @access public
	* @return bool
	*/
	function setCode($code) {
		if (!is_int($code))
			return false;
		$this->code=$code;
		return true;
	}/**/

	/**
	* sets faultString
	* @param string $string
	* @access public
	* @return bool
	*/
	function setString($string) {
		if (!is_string($string))
			return false;
		$this->string=$string;
		return true;
	}/**/

	/**
	* build self node and append it to parent node
	* @param ooXmlRpcDomNode &$node
	* @access public
	* @return bool
	*/
	function appendToNode(&$node) {
		if (!is_a($node,'ooxmlrpcdomnode'))
			return false;
		if (!$this->isValid())
			return false;
		// building struct value
		$data=array(
			'faultCode'=>$this->code
			,'faultString'=>$this->string
		);
		$struct=&new ooXmlRpcValue('struct',$data);
		// appending self <fault>
		$faultNode=&$node->makeChild('fault');
		// appending struct
		$struct->appendToNode($faultNode);
		return true;
	}/**/

	/**
	* check/tells if couple code/string is valid (upon types only)
	* @param
	* @access public
	* @return bool
	*/
	function isValid() {
		return (is_int($this->code) and is_string($this->string));
	}/**/

	/**
	* sets content from node
	* @param ooXmlRpcDomNode & $node
	* @access public
	* @return bool
	*/
	function setFromNode(& $node) {
		if (!is_a($node,'ooXmlRpcDomNode'))
			return false;
		// searching code
		if ($nodes=$node->getxpathnodes('child::value/struct/member[name=\'faultCode\']/value')) {
			$value=new ooxmlrpcvalue();
			$value->setfromnode($nodes[0]);
			$this->code=$value->getvalue();
		}
		// searching string
		if ($nodes=$node->getxpathnodes('child::value/struct/member[name=\'faultString\']/value')) {
			$value=new ooxmlrpcvalue();
			$value->setfromnode($nodes[0]);
			$this->string=$value->getvalue();
		}
		// echo wakaffvar($this);
		return true;
	}/**/

	/**
	*
	* @param
	* @access public
	* @return
	* /
	function () {

	}/**/
/*------------------------------------------------------------------------------
                                                                      properties
------------------------------------------------------------------------------*/
	/**
	* @var int faultCode
	*/
	var $code;
	/**
	* @var string faultString
	*/
	var $string='';

/*------------------------------------------------------------------------------
                                                                 private methods
------------------------------------------------------------------------------*/
	/**
	*
	* @param
	* @access protected
	* @return
	* /
	function () {

	}/**/

	/**
	*
	* @param
	* @access protected
	* @return
	* /
	function () {

	}/**/
/*------------------------------------------------------------------------------
                                                                 private methods
------------------------------------------------------------------------------*/
	/**
	*
	* @param
	* @access private
	* @return
	* /
	function () {

	}/**/

	/**
	*
	* @param
	* @access private
	* @return
	* /
	function () {

	}/**/
}

/**
*
* @author J.Ducastel <fredbird@fredbird.org>
* @version
*/
class ooXmlRpcValue {
/*------------------------------------------------------------------------------
                                                                     constructor
------------------------------------------------------------------------------*/
	/**
	*
	* @access
	*/
	function ooXmlRpcValue($type=null,$value=null) {
		$this->__construct($type,$value);
	}/**/

	/**
	* "real" constructor. Sets type and value if provided
	* @param string $type
	* @param mixed $value
	* @access public
	*/
	function __construct($type=null,$value=null) {
		$this->setType($type);
		$this->setValue($value);
	}/**/
/*------------------------------------------------------------------------------
                                                                  public methods
------------------------------------------------------------------------------*/
	/**
	* sets value from ooXmlRpcNode instance
	* @param ooxmlrpcnode &$node
	* @access public
	* @return bool
	*/
	function setFromNode(&$node) {
		// checking input
		if (!is_a($node,'ooxmlrpcdomnode')) {
			return false;
		}
		//var_dump($node);
		// searching for type = value child element tagname
		if ($typeNodes=$node->getxpathnodes('child::*')) {
			$type=$typeNodes[0]->getTagName();
			$valueXpath='child::*';
			//$valueNodes=$node->getXpathNodes('child::*');
		} else {
			// no child element, default to string
			$type='string';
			$valueXpath='text()';
			//$valueNodes=$node->getXpathNodes('text()');
		}
		$this->type=$type;
		// getting value content node(s)
		$valueNodes=$node->getXpathNodes($valueXpath);
		// extracting value, method depends on type
		switch($type) {
			// non-scalars
			case 'array':
				$this->value=array();
				// sub-values are within <data>
				$valueNodes=$valueNodes[0]->getXpathNodes('data/*');
				// cycling values
				for ($n=0; $n<count($valueNodes); $n++) {
					// building instance
					$value=&new ooXmlRpcValue();
					// setting from node
					$value->setFromNode($valueNodes[$n]);
					// importing instance
					$this->value[]=&$value;
				}
				break;
			case 'struct':
				$this->value=array();
				// sub-values are within <data>
				$valueNodes=$valueNodes[0]->getXpathNodes('member');
				// cycling values (<member> nodes)
				for ($n=0; $n<count($valueNodes); $n++) {
					// building instance
					$value=&new ooXmlRpcValue();
					// getting key
					$subKeyNodes=$valueNodes[$n]->getXpathNodes('name');
					$key=$subKeyNodes[0]->getContent();
					// getting value
					$subValueNodes=$valueNodes[$n]->getXpathNodes('value');
					// setting from node
					$value->setFromNode($subValueNodes[0]);
					// importing instance
					$this->value[$key]=&$value;
				}
				break;
			// scalars
			case 'boolean':
				$value=$valueNodes[0]->getContent();
				if ($value=='1' or strtolower($value)=='true')
					$this->value=true;
				else $this->value=false;
				break;
			case 'i4':
			case 'int':
			case 'integer':
				$value=$valueNodes[0]->getContent();
				$this->type='int';
				$this->value=intval($value);
				break;
			case 'double':
				$value=$valueNodes[0]->getContent();
				//$this->type='int';
				settype($value,'double');
				$this->value=&$value;
				break;
			case 'string':
				//$value=$valueNodes[0]->getContent();
				//$this->value=&$value;
				//break;
			case 'base64':
				//$value=$valueNodes[0]->getContent();
				//$this->value=base64_decode($value);
				//$this->value=&$value;
				//break;
			case 'dateTime.iso8601':
				//$value=$valueNodes[0]->getContent();
				//break;
			default: // scalar
				$this->value=$valueNodes[0]->getContent();
		}
		return true;
		//var_dump($valueNodes);
	}/**/

	/**
	* check/tells if the value is valid
	* @param mixed $value optional, checks current value if omitted
	* @access public
	* @return bool
	*/
	function isValid($value=null) {
		if (is_null($value))
			$value=&$this->value;
		switch($this->type) {
			// scalars
			case 'boolean':
				return in_array($value,array(0,1));
			case 'string':
				return is_string($value);
			case 'int':
				return is_int($value);
			case 'double':
				return is_double($value);
			case 'base64':
				return ereg('[a-zA-Z0-9+=/]+',$value);
			case 'dateTime.iso8601':
				return ereg('[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}',$value);
			// non-scalars
			case 'array':
				return is_array($value);
			case 'struct':
				return is_array($value) or is_object($value);
			default:
				return false;
		}
	}/**/

	/**
	* check/tells if value is a scalar
	* @param mixed $value optional, checks current value if not provided
	* @access public
	* @return
	*/
	function isScalar($value=null) {
		if (is_null($value))
			$value=&$this->value;
		return is_scalar($value);
	}/**/

	/**
	* return value's type
	* @access public
	* @return string int|string|boolean|base64|double|dateTime.iso8601|struct|array
	*/
	function getType() {
		return $this->type;
	}/**/

	/**
	* sets type (value has to be null)
	* @param string $type
	* @access public
	* @return bool
	*/
	function setType($type) {
		if (!in_array($type,array('string','int','double','boolean','base64','dateTime.iso8601','struct','array'))) {
			return false;
		}
		if (!is_null($this->value)) {
			return false;
		}
		$this->type=$type;
		return true;
	}/**/

	/**
	* sets value (type has to be set first, else it will be guessed
	* @param mixed $value
	* @access public
	* @return bool
	*/
	function setValue($value) {
		if (is_null($value))
			return false;
		// if type is not set, try guessing
		if (is_null($this->type) and $guessed=$this->guessType($value)) {
			// echo "<p>guessed $guessed</p>";
			$this->setType($guessed);
		}
		if (!$this->isValid($value))
			return false;
		// scalar
		// $this->value=&$value;
		switch ($this->type) {
			// non-scalars
			case 'array':
			case 'struct':
				if (is_object($value))
					$value=get_object_vars($value);
				if (!is_array($value))
					return false;
				foreach ($value as $key=>$item) {
					if (is_a($item,'ooxmlrpcvalue')) {
						$this->value[$key]=$item;
					} else {
						$subValue=&new ooXmlRpcValue(null,$item);
						if ($subValue->setValue($item))
							$this->value[$key]=&$subValue;
					}
				}
				break;
			// scalars
			case 'boolean':
				if ($value) $this->value=1;
				else $this->value=0;
				break;
			case 'int':
			case 'double':
			case 'base64':
			case 'string':
			case 'dateTime.iso8601':
				$this->value=&$value;
				break;
			default:
				return false;
		}

		return true;
	}/**/

	/**
	* build and append self node(s) to parent node
	* @param ooXmlRpcDomNode &$parentNode
	* @access public
	* @return bool
	*/
	function appendToNode(& $parentNode) {
		// build & append value node
		$valueNode=&$parentNode->makeChild('value');
		// build & append type node to value node
		$typeNode=&$valueNode->makeChild($this->type);
		// insert content into type node
		switch ($this->type) {
			// non-scalars
			case 'array':
				//$arrayNode=&$typeNode->makeChild('array');
				// append data node
				$dataNode=&$typeNode->makeChild('data');
				// append child values to data node
				for ($n=0; $n<count($this->value); $n++) {
					$this->value[$n]->appendToNode($dataNode);
				}
				break;
			case 'struct':
				// cycling through sub-value nodes
				if (!is_array($this->value)) return false;
				foreach ($this->value as $key=>$value) {
					// appending <member> nodes
					$memberNode=&$typeNode->makeChild('member');
					// appending <name>
					$nameNode=&$memberNode->makeChild('name');
					$nameNode->setContent($key);
					// appending <value>
					//$deb=&wak::getservice('wakdebug');
					//echo $deb->vardump($value);
					$value->appendToNode($memberNode);
				}
				break;
			//scalars
			default:
				$typeNode->setContent($this->value);
		}
		return true;
	}/**/

	/**
	* tries to guess a xml-rpc type for the var
	* @param
	* @access public
	* @return
	*/
	function guessType(&$var) {
		if (is_bool($var))
			return 'boolean';
		if (is_int($var))
			return 'int';
		if (is_double($var))
			return 'double';
		if (is_object($var))
			return 'struct';
		if (is_array($var)) {
			if (is_int(key($var)))
				return 'array';
			else return 'struct';
		}
		if (is_string($var) and ereg('[0-9]{6}T[0-9]{2}:[0-9]{2}:[0-9]{2}',$var))
			return 'dateTime.iso8601';
		if (is_string($var) and ereg('^[a-zA-Z0-9+/=]{50,}$',$var))
			return 'base64';
		if (is_string($var))
			return 'string';
		
		return false;
	}/**/

	/**
	* Return value
	* @access public
	* @return mixed
	*/
	function getValue() {
		if (!is_array($this->value))
				return $this->value;
		// recursion to get sub-values
		$value=array();
		foreach (array_keys($this->value) as $key) {
			$value[$key]=$this->value[$key]->getValue();
		}
		return $value;
	}/**/

	/**
	*
	* @param
	* @access public
	* @return
	* /
	function () {

	}/**/

	/**
	*
	* @param
	* @access public
	* @return
	* /
	function () {

	}/**/
/*------------------------------------------------------------------------------
                                                                      properties
------------------------------------------------------------------------------*/
	/**
	* @var type desc
	*/
	var $type; // int|boolean|string|base64|dateTime.iso8601|struct|array
	var $value;

/*------------------------------------------------------------------------------
                                                                 private methods
------------------------------------------------------------------------------*/
	/**
	*
	* @param
	* @access protected
	* @return
	* /
	function () {

	}/**/

	/**
	*
	* @param
	* @access protected
	* @return
	* /
	function () {

	}/**/
/*------------------------------------------------------------------------------
                                                                 private methods
------------------------------------------------------------------------------*/
	/**
	*
	* @param
	* @access private
	* @return
	* /
	function () {

	}/**/

	/**
	*
	* @param
	* @access private
	* @return
	* /
	function () {

	}/**/
}
?>
